﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using aboveXWords.Properties;

namespace aboveXWords.GUI
{
  [Serializable]
  public class Square
  {
    public Arrow Arrow { get; private set; }
    protected RectangleF m_rectangle;

    public Square(Square square)
    {
      Rectangle = square.Rectangle;
      Arrow = new Arrow();
    }

    public Square(float x, float y, float width, float height)
    {
      Rectangle = new RectangleF(x, y, width, height);
      Arrow = new Arrow();
    }

    protected Square()
    {
      Arrow = new Arrow();
    }

    protected internal bool Selected { get; set; }
    protected internal bool Moving { get; set; }
    protected internal bool Hover { get; set; }
    protected internal bool Cursor { get; set; }

    public virtual RectangleF Rectangle
    {
      get { return m_rectangle; }
      set
      {
        m_rectangle = value;
        CreateGraphicsPath();
      }
    }

    public virtual RectangleF Bound
    {
      get
      {
        RectangleF bound = m_rectangle;
        bound.Inflate(2, 2);
        return bound;
      }
    }

    protected GraphicsPath Path { get; set; }
    protected internal bool Error { get; set; }

    protected internal virtual void Paint(Graphics graphics)
    {
      if (Selected)
        graphics.FillPath(Painter.Colors.SquareSelectedBrush, Path);
      if (Error)
        graphics.FillPath(Painter.Colors.SquareErrorBrush, Path);
      graphics.DrawPath(Hover ? Painter.Colors.SquareHoverPen : Painter.Colors.SquarePen, Path);
      Arrow.Paint(graphics);
    }

    protected internal virtual void CreateGraphicsPath()
    {
      Path = new GraphicsPath();
      Path.AddRectangle(Rectangle);
    }

    protected internal virtual Cursor Move(Canvas canvas, MouseEventArgs mouseEventArgs,
                                           MouseEventArgs mouseDownPosition)
    {
      Moving = true;
      Invalidate(canvas);
      Rectangle = new RectangleF(Rectangle.Location.X + mouseEventArgs.Location.X - mouseDownPosition.X,
                                 Rectangle.Location.Y + mouseEventArgs.Location.Y - mouseDownPosition.Y,
                                 Rectangle.Width,
                                 Rectangle.Height);
      Arrow.Move(mouseEventArgs, mouseDownPosition);
      Invalidate(canvas);
      return Cursors.Cross;
    }

    private const int InsideCode = 0; // 0000
    private const int LeftCode = 1; // 0001
    private const int RightCode = 2; // 0010
    private const int BottomCode = 4; // 0100
    private const int TopCode = 8; // 1000

    // Compute the bit code for a point (x, y) using the clip rectangle
    // bounded diagonally by (xmin, ymin), and (xmax, ymax)
    private int ComputeOutCode(double x, double y)
    {
      int code = InsideCode;

      if (x < Rectangle.Left) // to the left of clip window
        code |= LeftCode;
      else if (x > Rectangle.Right) // to the right of clip window
        code |= RightCode;
      if (y < Rectangle.Top) // below the clip window
        code |= BottomCode;
      else if (y > Rectangle.Bottom) // above the clip window
        code |= TopCode;

      return code;
    }

    // Cohen–Sutherland clipping algorithm clips a line from
    // P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with 
    // diagonal from (xmin, ymin) to (xmax, ymax).
    public bool CohenSutherlandLineClip(ref double x0, ref double y0, ref double x1,
                                               ref double y1)
    {
      // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
      int outcode0 = ComputeOutCode(x0, y0);
      int outcode1 = ComputeOutCode(x1, y1);
      bool accept = false;

      while (true)
      {
        if ((outcode0 | outcode1) == 0)
        {
          //logical or is 0. Trivially accept and get out of loop
          accept = true;
          break;
        }
        if (outcode0 == InsideCode & outcode1 == InsideCode)
        {
          //logical and is not 0. Trivially reject and get out of loop
          break;
        }
        // failed both tests, so calculate the line segment to clip
        // from an outside point to an intersection with clip edge
        double x = 0;
        double y = 0;

        // At least one endpoint is outside the clip rectangle; pick it.
        int outcodeOut = outcode0 != InsideCode ? outcode0 : outcode1;

        // Now find the intersection point;
        // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
        if ((outcodeOut & TopCode) == TopCode)
        {
          // point is above the clip rectangle
          x = x0 + (x1 - x0) * (Rectangle.Bottom - y0) / (y1 - y0);
          y = Rectangle.Bottom;
        }
        else if ((outcodeOut & BottomCode) == BottomCode)
        {
          // point is below the clip rectangle
          x = x0 + (x1 - x0) * (Rectangle.Top - y0) / (y1 - y0);
          y = Rectangle.Top;
        }
        else if ((outcodeOut & RightCode) == RightCode)
        {
          // point is to the right of clip rectangle
          y = y0 + (y1 - y0) * (Rectangle.Right - x0) / (x1 - x0);
          x = Rectangle.Right;
        }
        else if ((outcodeOut & LeftCode) == LeftCode)
        {
          // point is to the left of clip rectangle
          y = y0 + (y1 - y0) * (Rectangle.Left - x0) / (x1 - x0);
          x = Rectangle.Left;
        }
        // Now we move outside point to intersection point to clip
        // and get ready for next pass.
        if (outcodeOut == outcode0)
        {
          x0 = x;
          y0 = y;
          outcode0 = ComputeOutCode(x0, y0);
        }
        else
        {
          x1 = x;
          y1 = y;
          outcode1 = ComputeOutCode(x1, y1);
        }
      }
      return accept;
    }

    protected internal bool LineClip(bool start, ref double x0, ref double y0, ref double x1, ref double y1)
    {
      bool success = CohenSutherlandLineClip(ref x0, ref y0, ref x1, ref y1);
      if (start)
      {
        x0 = x1;
        y0 = y1;
      }
      return success;
    }
    
    public bool Hit(MouseEventArgs mouseEventArgs)
    {
      return Rectangle.Contains(mouseEventArgs.Location);
    }

    public PointF Center
    {
      get { return new PointF(Rectangle.X + Rectangle.Width/2, Rectangle.Y + Rectangle.Height/2); }
    }

    internal void Invalidate(Canvas canvas)
    {
      canvas.InvalidateCanvas(Bound);
      Arrow.Invalidate(canvas);
    }

    internal void InvalidateSelected(Canvas canvas)
    {
      if (Selected)
        Invalidate(canvas);
    }

    public void Unselect(Canvas canvas)
    {
      if (Selected || Hover)
        InvalidateSelected(canvas);
      Hover = false;
      Selected = false;
      Moving = false;
    }

    public void Unhover(Canvas canvas)
    {
      if (Hover)
        Invalidate(canvas);
      Hover = false;
    }

    public bool Contains(Square square)
    {
      return Rectangle.IntersectsWith(square.Rectangle);
    }

    public virtual void Snap(PointF snap)
    {
      Moving = false;
      Rectangle = new RectangleF(Rectangle.X - snap.X,
                                  Rectangle.Y - snap.Y,
                                  Rectangle.Width,
                                  Rectangle.Height);
      Arrow.Snap(snap.X, snap.Y);
    }

    public virtual bool DropOk(Square square)
    {
      return true;
    }
  }
}